#pragma once
#include <memory>
#include <functional>

namespace DXBase
{
	// general pointer interface
	public interface class IPointer
	{
		property size_t Data { size_t get(); }
		property uint32 ByteSize { uint32 get(); }
	};

	// thin wrapper around raw native memory buffer
	public ref class MemoryBlock sealed : IPointer
	{
	private:
		uint32 capacity;
		void* data;
		bool owningData;

		virtual uint32 GetByteSize() = IPointer::ByteSize::get { return ByteSize; }

	public:
		MemoryBlock();
		MemoryBlock(uint32 cap);
		MemoryBlock(size_t data, uint32 len);
		virtual ~MemoryBlock();

		// whether this class own the buffer or not. If not it can't change the the data size.
		property bool MemOwner { bool get() { return owningData ; } }

		// the underlying pointer address
		virtual property size_t Data { size_t get() { return (size_t)data; } }
		// How many bytes have been allocated.
		property uint32 ByteSize 
		{
			uint32 get() { return capacity; } 
			void set(uint32 value);
		}

		void Memset(byte value)                                        { Memset(this, value, 0, ByteSize); }
		void Memset(byte value, uint32 offset, uint32 count)           { Memset(this, value, offset, count); }
		void Memmove(uint32 offsetSrc, uint32 offsetDst, uint32 count) { Memcopy(this, offsetSrc, this, offsetDst, count); }

		static void Memset(IPointer^ p, byte value) { Memset(p, value, 0, p->ByteSize); }
		static void Memset(IPointer^ p, byte value, uint32 offset, uint32 count);
		static void Memcopy(IPointer^ p, uint32 offsetSrc, IPointer^ npDst, uint32 offsetDst, uint32 count);

		MemoryBlock^ Copy()
		{
			auto res = ref new MemoryBlock();
			res->ByteSize = ByteSize;
			Memcopy(this, 0, res, 0, ByteSize);
			return res;
		}
	};

	public ref class PointerView sealed : IPointer
	{
	private:
		IPointer^ pointer;
		uint32 pos, len;
	internal:
		PointerView(IPointer^ p, uint32 pos);
		PointerView(IPointer^ p, uint32 pos, uint32 len);

	public:
		virtual property size_t Data { size_t get(); }
		virtual property uint32 ByteSize { uint32 get() { return len; } }
	};


	template <typename T>
	ref class TPointer
	{
	public:
		virtual ~TPointer()
		{
			if (trim)
				ptr->ByteSize = psize * sizeoft;
		}
	internal:
		uint32 psize;
		MemoryBlock^ ptr;
		static const uint32 sizeoft = sizeof(T);
		bool trim;

		TPointer(MemoryBlock^ p, bool trim = true, uint32 size = 0) : ptr(p), trim(trim), psize(size) { if (!p) throw ref new Platform::NullReferenceException(); }
		static property uint32 SizeOf { uint32 get() { return sizeoft; } }

		T& operator[] (uint32 x) { return ((T*)ptr->Data)[x]; }
		property uint32 Size
		{
			uint32 get() { return psize; }
			void set(uint32 n) 
			{
				auto value = n * sizeoft;
				if (value > ptr->ByteSize)
				{
					static const int Pow2 = 12;
					if (value > 1<<Pow2)
					{
						ptr->ByteSize = value - (value % (1<<10)) + (1<<10);
					}
					else
					{
						for(uint32 i=4; i<(Pow2+2); i++)
						{
							if (value < (uint32)(1<<i))
							{
								ptr->ByteSize = 1<<i;
								break;
							}
						}
					}
				}
				psize = n;
			}
		}
		void SetSizes(uint32 size)
		{
			ptr->ByteSize = size * SizeOf;
			psize = size;
		}
		TPointer<T>^ operator<<(const T& value)
		{
			auto s = Size;
			Size++;
			this[s] = value;
			return this;
		}
	};
}